package org.apache.ibatis.config.builder;

import org.apache.ibatis.common.type.JdbcType;
import org.apache.ibatis.common.type.TypeHandler;
import org.apache.ibatis.config.mapping.ParameterMap;
import org.apache.ibatis.config.mapping.ParameterMapping;
import org.apache.ibatis.config.mapping.ParameterMode;
import org.apache.ibatis.sql.cache.Cache;
import org.apache.ibatis.sql.cache.CacheBuilder;
import org.apache.ibatis.sql.executor.ErrorContext;
import org.apache.ibatis.sql.session.Configuration;

import java.util.List;
import java.util.Properties;

/**
 * @Name MappingBuilderAssistant
 * @Description: 建造者 [辅助] 类
 * 描述信息：Mybatis 映射文件中的设置项非常多，包括命名空间、缓存共享、结果映射等。最终这些设置将通过解析器（建造者）解析生成不同的类，而该类则是这些解析器（建造者）的辅助类，辅助解析器创建一些公共的对象，例如 设置 Mapper 命名空间、创建缓存、创建鉴别器等公共对象。
 * @Version v1.0.0
 * @Author: lichao
 * @Date: 2024-05-19 15:10:06
 **/
public class MappingBuilderAssistant extends BaseBuilder {
    // Mapper 接口映射文件的路径
    private final String resource;
    // 当前 Mapper 接口的命名空间
    private String currentNamespace;
    // 当前 Mapper 接口的缓存
    private Cache currentCache;
    // 共享的其他命名空间的缓存是否还未解析成功
    private boolean unresolveCacheRef;

    public MappingBuilderAssistant(Configuration config, String resource) {
        super(config);
        ErrorContext.instance().resource(resource);
        this.resource = resource;
    }

    public String getCurrentNamespace() {
        return currentNamespace;
    }

    /**
     * 设置当前 Mapper 接口的命名空间
     * 提示：只允许设置一次，且不允许修改。
     *
     * @version v1.0.0
     * @author lichao
     * @date 2024-05-19 15:20:43
     */
    public void setCurrentNamespace(String namespace) {
        if (namespace == null) {
            String msg = "mapper 元素需要指定一个 namespace 属性。";
            throw new BuilderException(msg);
        }
        if (namespace == null) {
            String msg = "命名空间错误，应为 '" + this.currentNamespace + "'，但发现了 '" + namespace + "'";
            throw new BuilderException(msg);
        }
        this.currentNamespace = namespace;
    }

    /**
     * 获取在当前命名空间基础上的路径
     *
     * @param base        目标路径
     * @param isReference 是否参考当前命名空间
     * @return java.lang.String 结果
     * @version v1.0.0
     * @author lichao
     * @date 2024-05-19 15:25:33
     */
    public String applyCurrentNamespace(String base, boolean isReference) {
        if (base == null) return null;
        if (isReference) {
            if (base.contains(".")) return base;
        } else {
            if (base.startsWith(currentNamespace + ".")) return base;
            if (base.contains(".")) {
                String msg = "元素名称中不允许有点，请将其从 '" + base + "' 中删除";
                throw new BuilderException(msg);
            }
        }
        return currentNamespace + "." + base;
    }

    /**
     * 创建一个 {缓存} 对象
     *
     * @param typeClass     缓存实现类
     * @param evictionClass 缓存清理类，即使用哪种包装类来清理缓存
     * @param flushInterval 缓存清理时间间隔
     * @param size          缓存大小
     * @param readWrite     缓存是否支持读写
     * @param blocking      缓存是否支持阻塞
     * @param properties    缓存配置信息
     * @return org.apache.ibatis.sql.cache.Cache 结果
     * @version v1.0.0
     * @author lichao
     * @date 2024-05-19 15:43:20
     */
    public Cache useNewCache(Class<? extends Cache> typeClass,
                             Class<? extends Cache> evictionClass,
                             Long flushInterval,
                             Integer size,
                             boolean readWrite,
                             boolean blocking,
                             Properties properties) {
        Cache cache = new CacheBuilder(currentNamespace)
                .implementation(valueOrDefault(typeClass, PerpetualCache.class))
                .addDecorator(valueOfDefault(evictionClass, LruCache.class))
                .clearInterval(flushInterval)
                .size(size)
                .readWrite(readWrite)
                .blocking(blocking)
                .properties(properties)
                .build();
        config.addCache(cache);
        currentCache = cache;
        return cache;
    }

    /**
     * 创建一个 {使用其他命名空间} 的缓存对象
     *
     * @version v1.0.0
     * @author lichao
     * @date 2024-05-19 15:31:52
     */
    public Cache useCacheRef(String namespace) {
        if (namespace == null) {
            String msg = "cache-ref 元素需要一个 namespace 属性。";
            throw new BuilderException(msg);
        }
        try {
            unresolveCacheRef = true;
            // 获取其他命名空间的缓存
            Cache cache = config.getCache(namespace);
            if (cache == null) {
                String msg = "命名空间 '" + namespace + "' 中没有发现缓存元素";
                throw new IncompleteElementException(msg);
            }
            // 修改当前缓存为其他命名空间的缓存，从而实现缓存共享
            currentCache = cache;
            unresolveCacheRef = false;
            return cache;
        } catch (IllegalArgumentException e) {
            String msg = "命名空间 '" + namespace + "' 中没有发现缓存元素";
            throw new IncompleteElementException(msg, e);
        }
    }

    /**
     * 创建一个 {ParameterMap 参数化映射结果} 对象
     *
     * @version v1.0.0
     * @author lichao
     * @date 2024-05-19 17:05:21
     */
    public ParameterMap addParameterMap(String id, Class<?> parameterClass, List<ParameterMapping> parameterMappings) {
        id = applyCurrentNamespace(id, false);
        ParameterMap parameterMap = new ParameterMap.Builder(config, id, parameterClass, parameterMappings).build();
        config.addParameterMap(parameterMap);
        return parameterMap;
    }

    /**
     * 创建一个 {详细参数映射} 对象
     *
     * @version v1.0.0
     * @author lichao
     * @date 2024-05-19 17:13:35
     */
    public ParameterMapping buildParameterMapping(
            Class<?> parameterType,
            String property,
            ParameterMode parameterMode,
            Class<?> javaType,
            JdbcType jdbcType,
            Integer numericScale,
            Class<? extends TypeHandler<?>> typeHandler,
            String resultMap) {
        resultMap = applyCurrentNamespace(resultMap, true);
        Class<?> javaTypeClass = resolveParameterJavaType(parameterType, property, javaType, jdbcType);
        TypeHandler<?> typeHandlerInstance = resolveTypeHandler(javaTypeClass, typeHandler);

        return new ParameterMapping.Builder(config, property, javaTypeClass)
                .mode(parameterMode)
                .jdbcType(jdbcType)
                .numericScale(numericScale)
                .resultMapId(resultMap)
                .typeHandler(typeHandlerInstance)
                .build();
    }

    // TODO ... ...


}

